#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>

int key_size = 8;
int buffer_size = 32;

//returns int 1 if the element already exists
int check_existence(int *array, int number) {

        for (int i=0; i<buffer_size; i++) {
                if (array[i] == number)
                        return 1;
         }
         return 0;
}

//returns free index
int find_empty_index(int *array) {

        for (int i=0; i<buffer_size; i++) {
                if (array[i] == buffer_size) return i;
        }
        return buffer_size;
}

//Fills the first elements based on the key
void first_fill(char *key_array, int *crypted_array) {

        int key_c;
        for (int i=0; i<key_size; i++) {
                key_c = (int)key_array[i]%buffer_size;
                if (!check_existence(crypted_array, key_c))
                        crypted_array[find_empty_index(crypted_array)] = key_c;
        }
}

//Number of free indexes, 0 if all are filled
int count_of_empty_bits(int *array) {
        int count=0;
        for (int i=0; i<buffer_size; i++) {
                if (array[i] == buffer_size) count++;
        }
        return count;
}

//Total count of elements a[0]+..+a[31]
int sum_of_elements(int *array) {
        int count=0;
        for (int i=0; i<buffer_size; i++)
                count+=array[i];
        return count;
}

void crypt(int *cr_array) {

        int new_key;
        int a_key;
        int add=0;

        while (true) {
                if(!count_of_empty_bits(cr_array)) break;

                a_key=sum_of_elements(cr_array)+ count_of_empty_bits(cr_array) + buffer_size + add;
                new_key=a_key%buffer_size;
                if (!check_existence(cr_array, new_key)) {
                        cr_array[find_empty_index(cr_array)] = new_key;
                        add+=1;
                } else
                add+=7;
        }
}


int find_indexnumber_by_value(int *array, int key) {
	for (int i=0; i<buffer_size; i++)
		if(array[i] == key) return i;
	return buffer_size;
}

void print_help(char *program_name) {
	fprintf(stderr, 
		"Usage: %s <-e|-d> -i filename -o filename [-p password]\n\n"
		"OPTIONS\n"
		"\t-e, --encrypt\n"
		"\t-d, --decrypt\n"
		"\t-i, --input-file  <filename>\n"
		"\t-o, --output-file <filename>\n"
		"\t-p, --password <password>\n"
		"\t-v, --verbose\n"
		"\t-h, --help\n\n"
		"EXAMPLES\n"
		"\t%s -e -i filename.tar -o filename.tar.en\n"
		"\t\twill create encrypted filename.tar.en\n"
		"\t%s -d -i filename.tar.en -o filename.tar\n"
		"\t\twill decrypt encrypted filename.tar.en to the filename.tar\n"
		"\t%s -e -i filename.tar -o filename.tar.en -p Pa$$w0rd\n"
		"\t\t the same as first but using given password\n"
		"\t\t by default you'll be prompted for a password\n"
		, program_name, program_name, program_name, program_name
	);
	exit(1);
}

int main(int argc, char** argv) {
	
	bool verbose = false;
	bool cr = true;
	char key[key_size];
	const char* input_filename = NULL;
	const char* output_filename = NULL;
	bool password_given = false;

	const char* const short_options = "i:o:edhvp:";
	static struct option long_options[] = {
		{ "help",		no_argument,		0, 'h'},
		{ "encrypt",		no_argument,		0, 'e'},
		{ "decrypt",		no_argument,		0, 'd'},
		{ "password",		required_argument,	0, 'p'},
		{ "input-file",		required_argument,	0, 'i'},
		{ "output-file",	required_argument,	0, 'o'},
		{ "vetbose",		no_argument,		0, 'v'},
		{0,0,0,0}
	};

	char *passwd;
	int next_option;
	int option_index;

	do {
		option_index = 0;
		next_option = getopt_long(argc, argv, short_options, long_options, &option_index);
		
		switch(next_option) {
			case 'h':
				print_help(argv[0]);
				break;
			case 'e':
				cr = true;
				break;
			case 'd':
				cr = false;
				break;
			case 'i':
				input_filename = optarg;
				break;
			case 'v':
				verbose = true;
				break;
			case 'o':
				output_filename = optarg;
				break;
			case 'p':
				passwd = optarg;
				password_given = true;
				break;
			case '?':
				print_help(argv[0]);
				break;
			case -1:
				break;
			default:	
				print_help(argv[0]);	
		}
	} while (next_option != -1);
	
	if (optind < argc) 
		 print_help(argv[0]);
 
	if (argc < 6)
		print_help(argv[0]);

	if(!password_given)
		passwd = getpass("Enter password(should be 3-8 symbols, other ones will be ignored):");
	
	for(int q=0; q<key_size; q++)
		key[q] = passwd[q];

	//printf("%s %d %d\n", key, (int)strlen(key), (int)strlen(passwd));
	
	if(strlen(passwd) < 3) {
		fprintf(stderr, "The lenght of key is %d, but it should be between 3 and %d\n", (int)strlen(passwd), key_size);
		print_help(argv[0]);
	}
	
	int cr_array[buffer_size];
	int dec_array[buffer_size];

        for (int j=0; j<buffer_size; j++)
                cr_array[j] = buffer_size; //Fill the array

        first_fill(key, cr_array);
        
	if(verbose) {
		for (int j=0; j<buffer_size; j++)
        		printf("%2d ", cr_array[j]);
			printf("\n");
	}

	crypt(cr_array);
	
	if(!cr) {
		for(int k=0; k<buffer_size; k++) {
			dec_array[k] =  find_indexnumber_by_value(cr_array, k);
		}
	}
	
	if(verbose) {
		for (int j=0; j<buffer_size; j++)
        		printf("%2d ", cr_array[j]);
			printf("\n");
	}
	
	//Open input file
	int input_fd = open(input_filename, O_RDONLY); //argv[1] :)
	if(input_fd == -1) {
		 perror(input_filename);
		 exit(1);
	}
	
	//Create crypted file filename.cr
	//char crypted_filename[256];
	//sprintf(crypted_filename, "%s.cr", *(argv+1));	
	int output_fd =  open(output_filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if(output_fd == -1) {
		perror(output_filename);
		close(input_fd);
		exit(1);
	}
        
	//Allocate src and dst buffer
	char *src_buffer;
	char *dst_buffer;
        if( ((src_buffer=(char *)malloc(buffer_size)) == NULL) || ((dst_buffer=(char *)malloc(buffer_size)) == NULL) ) {
                fprintf(stderr, "Can not allocate %d bytes of memory", buffer_size);
		close(input_fd);
		close(output_fd);
                exit(1);
        }

	long size = lseek(input_fd, 0L, SEEK_END);
	if (size == -1) { perror("lseek error: "); close(input_fd); close(output_fd); exit(1);}
	lseek(input_fd, 0, SEEK_SET);
	
	int bytes_read;
	
	do {
		bytes_read = read(input_fd, src_buffer, buffer_size);
		if(bytes_read != buffer_size) {
			if (verbose) printf("The sum of read bytes less than the buffer size: %d \n", bytes_read);
			write(output_fd, src_buffer, bytes_read);	
		}
		else {
			for (int i=0; i<buffer_size; i++) {
				if (cr)	dst_buffer[i] = src_buffer[cr_array[i]];
				else {
					dst_buffer[i] = src_buffer[dec_array[i]];
					//if(verbose) printf("i=%d index=%d\n", i, find_indexnumber_by_value(cr_array, i));
				}
			}
			write(output_fd, dst_buffer, bytes_read);
		}
	} while (bytes_read == buffer_size);


	printf("\n");
	close(input_fd);
	close(output_fd);
	free(src_buffer);
	free(dst_buffer);	
	return 0;
}
